package com.dib.neo.bkfs.fs;


import com.google.common.collect.Maps;

import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.channels.AsynchronousFileChannel;
import java.nio.channels.FileChannel;
import java.nio.channels.SeekableByteChannel;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.FileAttribute;
import java.nio.file.attribute.FileAttributeView;
import java.nio.file.spi.FileSystemProvider;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutorService;

public class BKFileSystemProvider extends FileSystemProvider {

  public static final String BKFILE_SCEME = "bkfs";
  private final Map<String, BKFileSystem> fsPool = Maps.newConcurrentMap();


  public BKFileSystemProvider() {
  }

  @Override
  public String getScheme() {
    return BKFILE_SCEME;
  }

  private void checkUri(URI var1) {
    if (!var1.getScheme().equalsIgnoreCase(this.getScheme())) {
      throw new IllegalArgumentException("URI does not match this provider");
    } else if (var1.getPath() == null) {
      throw new IllegalArgumentException("Path component is undefined");
    } else if (!var1.getPath().startsWith("/")) {
      throw new IllegalArgumentException("Path component should startsWith '/'");
    } else if (var1.getQuery() != null) {
      throw new IllegalArgumentException("Query component present");
    } else if (var1.getFragment() != null) {
      throw new IllegalArgumentException("Fragment component present");
    }
  }

  @Override
  public FileSystem newFileSystem(URI uri, Map<String, ?> var2) throws IOException {
    this.checkUri(uri);
    String connStr = getConnString(uri);
    BKFileSystem fs = fsPool.get(connStr);
    if (fs == null) {
      return getFileSystem(uri);
    }
    throw new FileSystemAlreadyExistsException();
  }

  @Override
  public FileSystem getFileSystem(URI uri) {
    this.checkUri(uri);
    String connStr = getConnString(uri);
    BKFileSystem fs = fsPool.get(connStr);
    if (fs == null) {
      fs = new BKFileSystem(this, connStr, uri.getPath());
      fsPool.put(connStr, fs);
    }
    return fs;
  }

  private String getConnString(URI uri) {
    StringBuilder s = new StringBuilder(uri.getHost());
    s.append(":").append(uri.getPort());
    return s.toString();
  }

  @Override
  public Path getPath(URI uri) {
    this.checkUri(uri);
    BKFileSystem fs = (BKFileSystem) this.getFileSystem(uri);
    return new BKPath(fs, uri.getPath());
  }

  @Override
  public FileChannel newFileChannel(Path obj,
      Set<? extends OpenOption> options,
      FileAttribute<?>... attrs)
      throws IOException {
    BKPath path = checkPath(obj);
    int mode = BKFileModeAttribute
        .toUnixMode(BKFileModeAttribute.ALL_READWRITE, attrs);
    try {
      return BKFile.newFileChannel(path, options, mode);
    } catch (BKException x) {
      x.rethrowAsIOException(path);
      return null;
    }
  }

  @Override
  public AsynchronousFileChannel newAsynchronousFileChannel(Path obj,
      Set<? extends OpenOption> options,
      ExecutorService executor,
      FileAttribute<?>... attrs) throws IOException {
//    BKPath file = checkPath(obj);
//    int mode = BKFileModeAttribute
//            .toUnixMode(BKFileModeAttribute.ALL_READWRITE, attrs);
//    ThreadPool pool = (executor == null) ? null : ThreadPool.wrap(executor, 0);
//    try {
//      return BKFile.newAsynchronousFileChannel(file, options, mode, pool);
//    } catch (BKException x) {
//      x.rethrowAsIOException(file);
//      return null;
//    }
    throw new UnsupportedOperationException();
  }

  private BKPath checkPath(Path obj) {
    return BKPath.toBKPath(obj);
  }


  @Override
  public SeekableByteChannel newByteChannel(Path obj,
      Set<? extends OpenOption> options,
      FileAttribute<?>... attrs)
      throws IOException {
    BKPath file = BKPath.toBKPath(obj);
    int mode = BKFileModeAttribute
        .toUnixMode(BKFileModeAttribute.ALL_READWRITE, attrs);
    try {
      return BKFile.newFileChannel(file, options, mode);
    } catch (BKException x) {
      x.rethrowAsIOException(file);
      return null;  // keep compiler happy
    }
  }

  @Override
  public DirectoryStream<Path> newDirectoryStream(Path dir,
      DirectoryStream.Filter<? super Path> filter) throws IOException {
    return new BKDirectoryStream(BKPath.toBKPath(dir), filter);
  }

  @Override
  public void createDirectory(Path dir, FileAttribute<?>... attrs) throws IOException {
    BKPath bkpath = BKPath.toBKPath(dir);
    bkpath.getFileSystem().bkfs().mkdirs(bkpath.getPath());
  }

  @Override
  public void delete(Path path) throws IOException {
    BKPath bkpath = BKPath.toBKPath(path);
    bkpath.getFileSystem().bkfs().rm(bkpath.getPath());
  }

  @Override
  public void copy(Path source, Path target, CopyOption... options) throws IOException {
    throw new UnsupportedOperationException();
  }

  @Override
  public void move(Path source, Path target, CopyOption... options) throws IOException {
    BKPath bksource = BKPath.toBKPath(source);
    BKPath bktarget = BKPath.toBKPath(target);
    bksource.getFileSystem().bkfs().mv(bksource.getPath(), bktarget.getPath());

  }

  @Override
  public boolean isSameFile(Path path, Path path2) throws IOException {
    return path.equals(path2);
  }

  @Override
  public boolean isHidden(Path path) throws IOException {
    return false;
  }

  @Override
  public FileStore getFileStore(Path path) throws IOException {
    BKPath bkpath = BKPath.toBKPath(path);
    return bkpath.getFileStore();
  }

  @Override
  public void checkAccess(Path path, AccessMode... modes) throws IOException {

  }

  @Override
  public <V extends FileAttributeView> V getFileAttributeView(Path path, Class<V> type,
      LinkOption... options) {
    return null;
  }

  @Override
  public <A extends BasicFileAttributes> A readAttributes(Path path, Class<A> type,
      LinkOption... options) throws IOException {
    return (A) BKPath.toBKPath(path).getFileAttributes();
  }

  @Override
  public Map<String, Object> readAttributes(Path path, String attributes, LinkOption... options)
      throws IOException {
    return null;
  }

  @Override
  public void setAttribute(Path path, String attribute, Object value, LinkOption... options)
      throws IOException {

  }

  public void free(BKFileSystem fs) {
    if (fs != null) {
      fsPool.remove(fs.getConnString());
    }
  }

  public static void main(String[] args) throws URISyntaxException {
    URI uri = new URI("bkfs://localhost:2181/d1/readme.txt");
    System.out.println("uri.getHost() = " + uri.getHost());
    System.out.println("uri.getPort() = " + uri.getPort());
    System.out.println("uri.getQuery() = " + uri.getQuery());
    System.out.println("uri.getPath() = " + uri.getPath());
    System.out.println("uri.getScheme() = " + uri.getScheme());

  }
}
